package org.example.testspring.context.impl;

import org.example.testspring.context.ApplicationContext;
import org.example.testspring.exception.NoBeanDefinedException;
import org.example.testspring.exception.NoBeanSetterException;
import org.example.testspring.exception.NoNamedBeanException;
import org.example.testspring.exception.NoSuchAttributeException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MyClassPathXmlApplicationContext implements ApplicationContext {

    Map<String,Object> beans;

    Map<String,String> destroyMethod;

    NodeList documentElement;

    /**
     * 通过构造函数传入配置文件路径
     * @param xml 配置文件路径
     */
    public MyClassPathXmlApplicationContext(String xml) throws NoSuchAttributeException, NoBeanDefinedException, IOException, ClassNotFoundException, NoNamedBeanException, NoBeanSetterException {
        beans = new HashMap<>();
        destroyMethod = new HashMap<>();
        parseXml(xml);
    }

    /**
     * 解析xml文件
     * @param xml 配置文件路径
     * @throws NoBeanDefinedException 没有bean定义异常
     */
    private void parseXml(String xml) throws NoBeanDefinedException, NoSuchAttributeException, IOException, ClassNotFoundException, NoNamedBeanException, NoBeanSetterException {
        if (beans.size() == 0) {
            try {
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                DocumentBuilder db = dbf.newDocumentBuilder();
                InputStream is = this.getClass().getClassLoader().getResourceAsStream(xml);
                if (is == null) {
                    throw new IOException("xml file not found");
                }
                Document document = db.parse(is);

                documentElement = document.getElementsByTagName("bean");
                if (documentElement == null || documentElement.getLength() == 0) {
                    throw new NoBeanDefinedException("No bean defined in xml");
                }
                parseBean(documentElement);
                injectDependency();
                documentElement = null;
            } catch (ParserConfigurationException | SAXException e) {
                e.printStackTrace();
            }

        }
    }

     /**
     * 解析bean
     * @param nodeList 节点列表
     * @throws NoSuchAttributeException 没有属性异常
     */
    private void parseBean(NodeList nodeList) throws NoSuchAttributeException, ClassNotFoundException {
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);

            Node id = node.getAttributes().getNamedItem("id");
            Node className = node.getAttributes().getNamedItem("class");
            Node initMethod = node.getAttributes().getNamedItem("init-method");
            Node destroyMethod = node.getAttributes().getNamedItem("destroy-method");

            if (id == null) {
                throw new NoSuchAttributeException("No attribute \"id\" in xml");
            }
            if (className == null) {
                throw new NoSuchAttributeException("No attribute \"class\" in xml");
            }


            String idValue = id.getNodeValue();
            String classNameValue = className.getNodeValue();

            if (destroyMethod != null) {
                this.destroyMethod.put(idValue, destroyMethod.getNodeValue());
            }

            Object bean = createClassFromClassName(classNameValue);
            if (initMethod != null) {
                invokeMethod(bean, initMethod.getNodeValue());
            }
            beans.put(idValue, bean);
        }
    }

    private void invokeMethod(Object bean, String nodeValue) {
        Method methods = null;
        try {
            methods = bean.getClass().getDeclaredMethod(nodeValue);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
            methods.invoke(bean);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 通过类名创建类
     * @param className 类名
     * @return 类
     */
    private Object createClassFromClassName(String className) throws ClassNotFoundException {
        try {
            return Class.forName(className).getDeclaredConstructor().newInstance();
        } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 注入依赖
     * @throws NoSuchAttributeException 没有属性异常
     */
    private void injectDependency() throws NoSuchAttributeException, NoNamedBeanException, NoBeanSetterException {
        for (int i = 0; i < documentElement.getLength();i++) {
            Node node = documentElement.item(i);
            Node id = node.getAttributes().getNamedItem("id");
            String idValue = id.getNodeValue();
            Object bean = beans.get(idValue);
            NodeList childNodes = node.getChildNodes();
            for (int j = 0; j < childNodes.getLength(); j++) {
                Node childNode = childNodes.item(j);
                if ("property".equals(childNode.getNodeName())) {
                    Node name = childNode.getAttributes().getNamedItem("name");
                    Node ref = childNode.getAttributes().getNamedItem("ref");
                    if (name == null) {
                        throw new NoSuchAttributeException("No attribute \"name\" in xml");
                    }
                    if (ref == null) {
                        throw new NoSuchAttributeException("No attribute \"ref\" in xml");
                    }
                    String nameValue = name.getNodeValue();
                    String refValue = ref.getNodeValue();
                    Object refBean = beans.get(refValue);
                    if (refBean == null) {
                        throw new NoNamedBeanException("No bean named " + refValue);
                    }
                    try {
                        bean.getClass().getDeclaredMethod("set" + nameValue.substring(0, 1).toUpperCase() + nameValue.substring(1),refBean.getClass().getInterfaces()[0]).invoke(bean, refBean);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }catch (NoSuchMethodException e) {
                        throw new NoBeanSetterException("No setter method in bean " + bean.getClass().getName());
                    }
                }
            }
        }
    }

    /**
     * 通过id获取bean
     * @param id id
     * @return bean
     */
    @Override
    public Object getBean(String id) throws NoNamedBeanException {
        if (beans.containsKey(id)) {
            return beans.get(id);
        }else {
            throw new NoNamedBeanException("No bean named " + id);
        }
    }



    @Override
    public void close() {
        invokeDestroyMethod();
        beans.clear();
    }

    private void invokeDestroyMethod() {
        for (String id : destroyMethod.keySet()) {
            Object bean = beans.get(id);
            invokeMethod(bean, destroyMethod.get(id));
        }
    }
}
